/*

*/

#include "SimpleFS.h"

void redirect(int argc, char * argv[], ifstream& infile_stream,  streambuf* infile_streambuf, 
	                                     ofstream& outfile_stream, streambuf* outfile_streambuf);

int print_initial_fs();

int test_carve();

int main(int argc, char * argv[])
{
	char fname[] = "main()";
	int retval;
	vector<string> command;
	string s, x, cmd;
	stringstream ss;
	unsigned int buffer;
	Inode * inode = new Inode();
	streambuf* infile_streambuf, * outfile_streambuf;
	ifstream   infile_stream;
	ofstream   outfile_stream;
		// LOOKS LIKE WE DON'T NEED THE REDIRECT COMMAND SINCE REG SHELL HANDLES IT
		// Parse command-line args.  Exit if invalid arguments entered.
		// Attempt to redirect stdin and stdout if user entered appropriate redirect commands.
	//	redirect(argc, argv, infile_stream, infile_streambuf, outfile_stream, outfile_streambuf);

	// Attempt to open our fs (for users who have already created it)
	// This open attempt will fail if the file does not yet exist (fstream class)
	fs.open(FS_NAME);
	// NEW USER (fs NOT found in their current dir)
	if(fs.fail()) {

		retval = mkfs_handle_errors_keep_file_open(fname);
		if(retval < 0 ) return retval;
	
	} // end handling first-time user (setting up fs)
	// RETURNING USER (fs found in their current dir)
	else {
		cout << "\nWelcome back to the file system of the ages!\n" << endl;
		/*
		//////////////////////////////////////////////////////////////////
		// TEMP SECTION: FOR DEVELOPMENT PURPOSES, DELETE OLD FILE, SO mkfs() RUNS AGAIN
		cout << "\nTemporarily I'm deleting the old file so that mkfs() runs..." << endl;
		fs.close();   // must close the file before we can delete it
		if(fs.fail()) {	cout <<"\nfs.close() failed\n"<<endl; if(WINDOWS_DEV) system("pause"); exit(-1);	}
		// delete old fs file
		bool fSuccess = DeleteFile(TEXT(FS_NAME));
    if (!fSuccess) { printf ("DeleteFile failed (%d)\n", GetLastError()); if(WINDOWS_DEV) system("pause"); exit(-12);  }
		// run mkfs from scratch
		retval = mkfs_handle_errors_keep_file_open();
		if(retval < 0 ) return -1;
		// END TEMP SECTION ///////
		///////////////////////////
		*/
	
		retval = init_return_user();
		if( retval < 0 ) return retval;
		// Make sure the FS I/O obj file is still open at this point
		retval = verify_fs_open(fname);
		if( retval < 0 ) return retval;
	
	} // end handling return user

	// TESTING:  try reading back in what we've written to examine things
	//print_initial_fs();

	// TESTING: try requesting & freeing different disk memory amounts
	//carve_test_main();

	////////////////////////////////////
	//Command line stuff starts here
	////////////////////////////////////
	do
	{
		command.clear();
		
		cout << ">>>";

		getline(cin, s);

		//Parse parameters, if any using a stringstream (I love these things!)
		ss.str(s);
		while(ss >> x)
			command.push_back(x);
		ss.clear();

		cmd = "";  // empty string initially in case user hits "enter" only (so loop will continue)
		// guard against seg fault if user hits "enter" only at prompt
		if(command.size() == 0 )  {  cout << endl;  continue;  	}
		else { cmd = command[0];  }  // OK to dereference now that we know vec contains something
		// HANDLE COMMANDS
		if(cmd == "carve")
			carve_test_main();
		else if(cmd == "mkdir") {
			if(command.size() == 2 ) {	fs_mkdir(command[1].data());  }
			else { 		cout << "\nIncorrect usage. Usage: mkdir <dir_name>" << endl;  }
		}
		else if(cmd == "rmdir")
			if(command.size() == 2 ) {	fs_rmdir(command[1].data());  }
			else { 		cout << "\nIncorrect usage. Usage: rmdir <dir_name>" << endl;  }
		else if(cmd == "ls")
			print_current_directory();
		else if(cmd == "cd" || cmd == "chdir")
		{
			if(command.size() > 1)
				fs_chdir(command[1].data());
			else
				fs_chdir("/");

			pwd(cout);
		}
		else if(cmd == "pwd")
			pwd(cout);

		else if(cmd == "create")
		{
			if(command.size() == 3 && isdigit(command[2][0]))
			{	
				int size;
				ss.str(command[2]);
				ss >> size;
				ss.clear();
				create_file(command[1].data(), size);
			}
			else { 		cout << "\nIncorrect usage. Usage: create <file_name> <size>" << endl;  }
		}
		else if (cmd == "open")
		{
			if(command.size() == 3 && (command[2] == "r" || command[2] == "w" || command[2] == "rw"))
			{	
				int flag, fd;
				string user_flag;
				ss.str(command[2]);
				ss >> user_flag;
				ss.clear();
				if( user_flag == "r" ) flag = FS_O_RDONLY;
				else if( user_flag == "w" ) flag = FS_O_WRONLY;
				else if( user_flag == "rw" ) flag = FS_O_RDWR;  // other entries will be caught in "else" below
				fd = fs_open(command[1].data(), flag);
				cout << "\nSuccess, fd = " << fd << endl;
			}
			else { 		cout << "\nIncorrect usage. Usage: open <file_name> <flag: {r,w,rw}>" << endl;  }
		}
		else if (cmd == "close")
		{
			if(command.size() == 2 && isdigit(command[1][0]))
			{	
				int fd;
				ss.str(command[1]);
				ss >> fd;
				ss.clear();
				fs_close(fd);
			}
			else {		cout << "\nIncorrect usage. Usage: close <file_descriptor>" << endl;  }
		}
		else if (cmd == "write")
		{
			int bytes;
			if(command.size() >= 3 && isdigit(command[1][0]))
			{
				int fd;
				ss.str(command[1]);
				ss >> fd;
				ss.clear();
				string data;
				
				for(int i = 2; i < command.size(); i++) {
					
					if(i < (command.size() -1)) data += command[i] + " ";
					else data += command[i];
				}
				
				bytes = fs_write(fd, data.data(), data.size());

				if(bytes != -1)
					cout << "\nSuccessfully wrote " << bytes << " bytes." << endl;
				else
					cout << "\nWrite failed." << endl;
			}
			else{	cout << "\nIncorrect usage. Usage: write <file_descriptor> <string>" << endl;  }
		}
		else if (cmd == "read")
		{
			int bytes;
			if(command.size() == 3 && isdigit(command[1][0]) && isdigit(command[2][0]))
			{
				int fd, count;
				ss.str(command[1]);
				ss >> fd;
				ss.clear();
				ss.str(command[2]);
				ss >> count;
				ss.clear();
				char * buff = new char[count];
				
				bytes = fs_read(fd, buff, count);

				if(bytes != -1)
				{
					cout << "\nSuccessfully read " << bytes << ":\n\n";

					for(int i = 0; i < bytes; i++)
						cout << buff[i];

					cout << endl;
				}
				else
					cout << "\nRead failed." << endl;
			}
			else{	cout << "\nIncorrect usage. Usage: read <file_descriptor> <number of bytes>" << endl;  }
		}
		else if (cmd == "seek")
		{
			int pos;
			if(command.size() == 3 && isdigit(command[1][0]) && isdigit(command[2][0]))
			{
				int fd, offset;
				ss.str(command[1]);
				ss >> fd;
				ss.clear();
				ss.str(command[2]);
				ss >> offset;
				ss.clear();
				
				pos = fs_seek(fd, offset);

				if(pos != -1)
					cout << "Success!" << endl;
				else
					cout << "\nSeek failed." << endl;
			}
			else{	cout << "\nIncorrect usage. Usage: seek <file_descriptor> <offset>" << endl;  }
		}
		else if(cmd == "cat")
		{
			if(command.size() == 2)
			{
				fs_cat(command[1].data());
			}
			else
			{
				cout << "\nIncorrect usage. Usage: cat <filename>" << endl;
			}
		}
		else if( cmd == "stat" )
		{
			if(command.size() == 2 ) {
				string filename = command[1];
				fileStat* buf = new fileStat;
				fs_stat(filename.c_str(), buf);
			}
			else {	cout << "\nIncorrect usage. Usage: stat <filename>" << endl;  }
		}
		else if( cmd == "tree" ) {
			print_directory();
		}
		else if( cmd == "tree_stat" ) {
			if(command.size() == 2 ) {
				tree_stat((command[1]).c_str());
			}
			else { cout << "\nIncorrect usage. Usage: tree_stat <pathname>" << endl; }
		}
		else if( cmd == "link" ) {

			if(command.size() == 3 ) {
				string src_path = command[1];
				string dest_path = command[2];
				int retval = fs_link(src_path.c_str(), dest_path.c_str());
				if(retval < 0 ) { cout << "\nlink command failed" << endl;  }
			}
			else {	cout << "\nIncorrect usage. Usage: link <source_path> <destination_path>" << endl;  }
		}
		else if( cmd == "unlink" ) {
			if(command.size() == 2 ) {

				int retval = fs_unlink(command[1].c_str());
				if(retval < 0) { cout << "\nunlink command failed" << endl;  }
			}
			else { cout << "\nIncorrect usage. Usage: unlink <path>" << endl;  }
		}
		else if(cmd == "import")
		{
			if(command.size() == 3)
				fs_import(command[1].data(), command[2].data());
			else
				cout << "\nIncorrect usage. Usage: import <source> <destination>" << endl;
		}
		else if(cmd == "export")
		{
			if(command.size() == 3)
				fs_export(command[1].data(), command[2].data());
			else
				cout << "\nIncorrect usage. Usage: export <source> <destination>" << endl;
		}
		else if(cmd == "cp")
		{
			if(command.size() == 3)
				fs_copy(command[1].data(), command[2].data());
			else
				cout << "\nIncorrect usage. Usage: cp <source> <destination>" << endl;
		}
		else if(cmd == "exit")//Close all the open files
		{
			map<int, Oft_entry>::iterator it;
			
			for(it = oft.begin(); it != oft.end(); it++)
				fs_close(it->first);
		}
		else if(cmd == "print_flist") {
			print_FList();
		}
		else if(cmd == "mkfs") 
		{
			int retval = fs_mkfs();

		}



		else {
			if(cmd != "exit")
				cout << "\nCommand \""<< cmd<< "\" not valid (or not implemented yet)." << endl;
		}

	}while(cmd != "exit");
	/// end command line stuff ///


	fs.close();
	system("pause");
	return 0;
} /// end main() ///



void redirect(int argc, char * argv[], ifstream& infile_stream,  streambuf* infile_streambuf, 
	                                     ofstream& outfile_stream, streambuf* outfile_streambuf)
{

	// TODO: handle case like: "shell  <testinput.txt  >testout.txt"  (no space after the "<" or ">") ?

	// TODO: handle finding redirect commands in any order


	streambuf* backup_stream_buf;

	string arg1 = argv[1];
	string arg2 = argv[2];
	string arg3 = argv[3];  // ">" for now
	string arg4 = argv[4];  // "testout.txt" for now

	/////////////////////////////////
	// try redirecting cout ////////

	outfile_stream.open(arg4);

  backup_stream_buf = cout.rdbuf();     // back up cout's streambuf

  outfile_streambuf = outfile_stream.rdbuf();   // get file's streambuf
  cout.rdbuf(outfile_streambuf);              // assign streambuf to cout

  cout << "\nThis is written to the file\n";
  
  cout.rdbuf(backup_stream_buf);        // restore cout's original streambuf

  outfile_stream.close();

} ///////// end redirect() ///////////////////





// Used for testing
int print_initial_fs()
{
	//const int buffsize = 256;
	// char buf[buffsize];
	char* func_name = "print_initial_fs()";
	// Make sure fs I/O obj still has file open
	if(! fs.is_open() ) { 
		fs.open(FS_NAME);  // leave this open until the end of main
		if(fs.fail()) { cout << "\nError: FS cannot be opened.\n"; return -1;		}
		cout << "\nfs file reopened in print_initial_fs()\n" << endl;
	}

	// TESTING:  try reading back in what we've written to examine things
	cout << "\n\nNow in print_initial_fs(), let's read in stuff from disk to test it out.\n" << endl;

	unsigned int test;

	// Test reading in the free-head ptr
	fs.type_read(FREE_HEAD_PTR_POSITION, &test, func_name);
	cout << "\nUsing only fs.type_read(), I read in the free-head offset as <" << test << ">\n" << endl;
	// Test reading in the only free-list node values
	fs.type_read_at_cur_pos( &test, func_name );
	cout << "\nI read in f.size as <" << test << ">\n" << endl;
	fs.type_read_at_cur_pos( &test, func_name );
	cout << "I read in f.flink as <" << test << ">\n" << endl;
	fs.type_read_at_cur_pos( &test, func_name );
	cout << "I read in f.blink as <" << test << ">\n" << endl;
	
	// Test reading in the offset to the root inode from disk
	unsigned int offset; // in this case 'offset' is the var I want to fill in from disk (b/c I'm reading in a "ptr")
	fs.type_read(ROOT_INODE_PTR_POSITION, &offset, func_name );
	cout << "\nRead in the Root inode offset <" << offset << ">" << endl;

	// Test reading in the root Inode chunk size (located 8 bytes before user's chunk returned by carve)
	fs.type_read(offset - 8, &test, func_name);
	cout << "\nFor the Inode allocated, user's chunk size is <" << test << ">\n" << endl;

	// Test reading in the actual root inode from disk
	Inode* inode = new Inode;

	fs.fread(offset, (char*)inode, sizeof(Inode), func_name );
	cout << "\nUsing the root offset, read in the root inode: inode->name <" << inode->name 
		   << "> inode->dir <" << (bool) inode->dir << ">" << endl;

	return 0;
} /// end print_initial_fs() ///



int test_carve()
{
	char* func_name = "test_carve()";
	unsigned int returned_mem_offset;
	unsigned int offset;
	unsigned int free_size;
	FList_node* f = new FList_node;

	cout << "\nNow in test_carve().  ";
	cout <<"\nI'll try to use carve() to get the entire free chunk." << endl;
	// first get the "ptr" to the free-list head
	fs.type_read(FREE_HEAD_PTR_POSITION, &offset, func_name);
	// next get the size of the one free-list node (easiest to read in the whole node first though)
	read_flist_nd(offset, f, func_name );
	
	///////////////////////////////////////////////////////////////////////////////////////////
	///// CASE 1: First request uses the last of the free space, second request tries for more
	unsigned int bytes = f->size - BOOKKEEP;
	// Request all available bytes (or enough that there won't be a free-list node leftover)
	returned_mem_offset = carve(bytes);
	cout << "\nI requested <" << bytes << "> bytes from carve(). This should have depleted the free-list." 
		   << "\ncarve() returned an offset of <" << returned_mem_offset << ">"
		   << "\nNext I'll try to request more bytes..." << endl;
	
	// Now try to request more bytes (there shouldn't be any avail)
	bytes = 100;
	returned_mem_offset = carve(bytes);
	cout << "\nI requested <" << bytes << "> bytes from carve(). This should have been unsuccessful." 
		   << "\ncarve() returned an offset of <" << returned_mem_offset << ">"
		   << "\n(an offset of 0 means carve() could not satisfy the request)" << endl;
	
	/*
	///////////////////////////////////////////////////////////////////////////////////////////
	///// CASE 2: Like case 1, but verifies it rounds req bytes up to nearest 8-byte qty
	unsigned int bytes = f->size - BOOKKEEP - 5;  // should get rounded up to nearest 8-byte qty.
	// Request all available bytes (or enough that there won't be a free-list node leftover)
	returned_mem_offset = carve(bytes);
	cout << "\nI requested <" << bytes << "> bytes from carve(). This should have depleted the free-list." 
		   << "\ncarve() returned an offset of <" << returned_mem_offset << ">"
		   << "\nNext I'll try to request more bytes..." << endl;
	// Now try to request more bytes (there shouldn't be any avail)
	bytes = 100;
	returned_mem_offset = carve(bytes);
	cout << "\nI requested <" << bytes << "> bytes from carve(). This should have been unsuccessful." 
		   << "\ncarve() returned an offset of <" << returned_mem_offset << ">"
		   << "\n(an offset of 0 means carve() could not satisfy the request)" << endl;
  */
	/*
	///////////////////////////////////////////////////////////////////////////////////////////
	///// CASE 3: A request is too large for any free-list node
	unsigned int bytes = f->size; // note that this should be too large to handle, since it needs BOOKEEP bytes
	
	returned_mem_offset = carve(bytes);
	cout << "\nI requested <" << bytes << "> bytes from carve()." 
		   << "\ncarve() returned an offset of <" << returned_mem_offset << ">" << endl;
  */
	return 0;
} ////// end test_carve() ////////


